package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;


/**
 * @author wk
 * @create 2021-09-06 20:43
 * 配置全局过滤器
 * spring gateWay默认9个过滤器
 * <p>
 * GateWay底层 WEBFLUX 不是tomcat底层的 Servlet
 */
@Component
public class GatewayFilter implements GlobalFilter, Ordered {
    public static final String TOKEN = "token";

    public static final String USER_LOGIN_KEY_PREFIX = "user:login:";

    // 路径匹配
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate redisTemplate;

    @Value("${require.login.url}")
    private String[] urls;
    public static final String LOGIN_URL =
            "http://passport.gmall.com/login.html?originUrl=";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        // 1.获取用户id
        String userId = getUserId(request);
        // 2.判断是否是内部资源 http://api.gmall.com/api/product/inner/haha/hehe
        String path = request.getURI().getPath();  // 请求路径
        if (antPathMatcher.match("/**/inner/**", path)) {
            // 内部资源 不允许访问
            return out(response, ResultCodeEnum.PERMISSION);
        }
        // 3.验证身份
        // 登录分为两种异步和同步
        // 如果是异步 弹出弹出框 浏览器路径并不发生改变
        // 对于这种/api/order/auth/submitOrder请求
        if (antPathMatcher.match("/**/auth/**", path) && StringUtils.isEmpty(userId)) {
            return out(response, ResultCodeEnum.LOGIN_AUTH);
        }

        // 4.如果是同步请求   浏览器路径会发生改变 跳转到登录页面
        // 对于 trade.html pay.html addCart.html 等 需要其他方式处理
        for (String url : urls) {
            // 遍历配置的需要登录的路径
            // 如果需要访问的路径中包含配置的需要登录的路径 而且此时并未查到用户id 则重定向去登录
            if (path.contains(url) && StringUtils.isEmpty(userId)) {
                // 303设置状态码 重定向
                response.setStatusCode(HttpStatus.SEE_OTHER);
                try {
                    // 重定向到登录页面
                    // 重定向设置地址 需要对originUrl进行编码转义 保证登录完成后从哪来回哪去
                    response.getHeaders().add(HttpHeaders.LOCATION,
                            LOGIN_URL + URLEncoder.encode(request.getURI().toString(), "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                return response.setComplete();
            }
        }
        // 放行之前需要 将用户id传递下去 添加购物车微服务处会使用
        // 创建一个新的请求将之前的请求信息复制并且同时携带userId 或者临时用户id
        request.mutate().header("userId", userId);
        String userTempId = getUserTempId(request);
        request.mutate().header("userTempId", userTempId);
        return chain.filter(exchange);
    }

    // 获取临时用户id
    private String getUserTempId(ServerHttpRequest request) {
        // 需要登录的页面会判断是否登录和是否存在临时用户，如果都没有，添加临时用户
        // 发送的是Ajax异步请求
        // 可能在cookie中也可能在请求头中
        String userTempId = request.getHeaders().getFirst("userTempId");
        // 请求头中如果没有
        if (StringUtils.isEmpty(userTempId)) {
            // cookie中
            HttpCookie httpCookie = request.getCookies().getFirst("userTempId");
            if (httpCookie != null) {
                userTempId = httpCookie.getValue();
            }
        }
        return userTempId;
    }

    // 返回方法
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum result) {
        Result<Object> build = Result.build(null, result);

        String jsonString = JSONObject.toJSONString(build);
        DataBufferFactory bufferFactory = response.bufferFactory();
        DataBuffer dataBuffer = bufferFactory.wrap(jsonString.getBytes());
        // 避免乱码设置编码格式
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        return response.writeWith(Mono.just(dataBuffer));
    }

    private String getUserId(ServerHttpRequest request) {
        // 1.获取token
        //  token 可能在请求头中 也可能在 cookie
        //  1.1如果页面发送的ajax异步请求 不携带cookie
        String token = request.getHeaders().getFirst(TOKEN);
        if (StringUtils.isEmpty(token)) {
            // 1.2如果token在cookie中 同步请求携带cookie
            HttpCookie httpCookie = request.getCookies().getFirst(TOKEN);
            if (httpCookie != null) {
                token = httpCookie.getValue();
            }
        }
        // 判断token是否存在
        if (!StringUtils.isEmpty(token)) {
            if (redisTemplate.hasKey(USER_LOGIN_KEY_PREFIX + token)) {
                // 如果不为空则从redis中获取
                return (String) redisTemplate.opsForValue().get(USER_LOGIN_KEY_PREFIX + token);
            }
        }
        return null;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
